package controller;

import java.awt.Point;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import utils.PlayerSides;

import model.Model;
import model.pieces.Piece;

/**
 * Class enables checking if game is in Check, Checkmate or Stalemate
 */
public class ThreatAnalyzer implements KingThreat {

	private Model gameBoard;
	private GameManager gameManager;
	
	/**
	 * Updates data of the player that was attacked on last turn.
	 * Data includes King's position, King's moves and positions of the friendly pieces.
	 * Function updates the given references
	 * @param attackedKingPosition- King's position
	 * @param attackedKingMoves- King's moves
	 * @param friendlyPiecesPositions- positions of friendly pieces
	 * @param currPlayerSide- indicates which player plays now
	 */
	private void getAttackedPlayerData(Point attackedKingPosition, List<Point> attackedKingMoves, Set<Point> friendlyPiecesPositions, PlayerSides.Sides currPlayerSide) {
		
		// get the attacked player's data
		if (currPlayerSide.equals(PlayerSides.Sides.Black)) {
			
			// get second player's King's position
			attackedKingPosition.move(gameBoard.getSecondKingPosition().x, gameBoard.getSecondKingPosition().y);
			
			// get second player's King's moves
			attackedKingMoves.addAll(gameBoard.getSecondKing().getAvailableDestinations(gameBoard.getBoard(), attackedKingPosition));
			
			// get second player's pieces positions
			friendlyPiecesPositions.addAll(gameBoard.getSecondPlayerPieces().keySet());
			
		} else {
			
			// get first player's King's position
			attackedKingPosition.move(gameBoard.getFirstKingPosition().x, gameBoard.getFirstKingPosition().y);
			
			// get first player's King's moves
			attackedKingMoves.addAll(gameBoard.getFirstKing().getAvailableDestinations(gameBoard.getBoard(), attackedKingPosition));
			
			// get first player's pieces positions
			friendlyPiecesPositions.addAll(gameBoard.getFirstPlayerPieces().keySet());
		}
		
		// all data has been initialized
		return;
	}
	
	/**
	 * Scans all of attacked King's moves and checks if there is at least one friendly piece
	 * that can block the attack.
	 * If each of the attacked King's moves results in Check and no friendly piece can block the attack, 
	 * returns True. Otherwise, returns False
	 * @param isFirstPlayerTurn- indicates currently playing player
	 * @return True if attacked player cannot move, False if can move
	 */	
	private boolean cannotMove(PlayerSides.Sides currPlayerSide) {
		
		// check if attacked King was captured
		Point kingPosition;
		if (currPlayerSide.equals(PlayerSides.Sides.Black)) { // first player played last
			kingPosition = gameBoard.getSecondKingPosition();
		} else {
			kingPosition = gameBoard.getFirstKingPosition();
		}
		
		if (kingPosition == null) {
			// King was captured, Checkmate
			return true;
		}
		
		// get attacked King's moves, its positions and friendly pieces positions
		List<Point> attackedKingMoves = new LinkedList<Point>();		
		Point attackedKingPosition = new Point();
		Set<Point> friendlyPiecesPositions = new HashSet<Point>();
		getAttackedPlayerData(attackedKingPosition, attackedKingMoves, friendlyPiecesPositions, currPlayerSide);
		
		// check if attacked King is surrounded by friendly pieces
		if (attackedKingMoves.isEmpty()) {
			
			// attacked King is protected
			return false;
		}
		
		// check if each move results in Check
		Iterator<Point> iter = attackedKingMoves.iterator();
		while (iter.hasNext()) {
			
			// get current move
			Point currMove = iter.next();
			
			// check if current move is not safe
			if (moveNotSafe(attackedKingPosition, currMove, currPlayerSide)) {
				
				// remove current move from list
				iter.remove();
			}
		}
		
		// check if there are friendly pieces that can block the attack
		boolean canBlockAttack = false;
		for (Point currPiecePosition : friendlyPiecesPositions) {
			
			// check if current piece has a move that does not result in check
			List<Point> currPieceMoves = gameManager.getSafeDestinations(currPiecePosition);
			for (Point currMove : currPieceMoves) {
				
				if (!moveNotSafe(currPiecePosition, currMove, currPlayerSide)) {
					
					// current piece can block the attack
					canBlockAttack = true;
				}
			}
		}
		
		// check if attacked King has any safe moves or attack can be blocked
		if (!attackedKingMoves.isEmpty() || canBlockAttack) {
			
			// at least one safe move is possible or can block the attack			
			return false;
		}
		
		// King has no safe move and no friendly piece can block the attack
		return true;
	 }

	/**
	 * Constructor- creates a new hazard-checker object
	 * @param gameBoard- reference to game board
	 * @param gameManager- reference to game manager
	 */
	public ThreatAnalyzer(Model gameBoard, GameManager gameManager) {
		
		// set members according to given values
		this.gameBoard = gameBoard;
		this.gameManager = gameManager;
	}
	
	/**
	 * Returns True if game is in Check, or False if not.
	 * Check is determined if the attacking player has a piece
	 * that can reach the attacked King's position 
	 */
	@Override
	public boolean isCheck(PlayerSides.Sides currPlayerSide) {
		
		// get attacked King's position and opponent's pieces
		Point attackedKingPosition;
		Set<Point> opponentPiecePositions;
		PlayerSides.Sides prevPlayerSide = PlayerSides.getOpponentSide(currPlayerSide);

		 if (prevPlayerSide.equals(PlayerSides.Sides.White)) {
			 
			 // get second player's King position
			 attackedKingPosition = gameBoard.getSecondKingPosition();
			 
			 // get all first player's pieces
			 opponentPiecePositions = gameBoard.getFirstPlayerPieces().keySet();
		 
		 } else {
			 
			 // get first player's King position
			 attackedKingPosition = gameBoard.getFirstKingPosition();
			 
			 // get all second player's pieces
			 opponentPiecePositions = gameBoard.getSecondPlayerPieces().keySet();
		 }
		 
		 // scan all opponent's pieces
		 for (Point currPiecePoisition : opponentPiecePositions) {
			 
			 // check if current piece can reach the attacked King
			 if (gameBoard.getPiece(currPiecePoisition.x, currPiecePoisition.y).getAvailableDestinations(gameBoard.getBoard(), currPiecePoisition).contains(attackedKingPosition)) {
				 
				 // current piece threats King
				 return true;
			 }
		 }
		
		 // no piece threats the King
		 return false;
	}
	
	/**
	 * Gets source position, destination position and indicator for currently playing player.
	 * Makes given move and checks if the move results in Check (friendly King being threatened).
	 * If given move threats friendly King, returns True. Otherwise, False.
	 * After making the move, board is restored (move is undone)
	 */
	@Override
	public boolean moveNotSafe(Point srcPosition, Point destPosition, PlayerSides.Sides currPlayerSide) {

		// default return value
		boolean notSafe = false;
		
		// check if destination position is free
		if (gameBoard.getPiece(destPosition.x, destPosition.y) == null) {
			
			// make move
			gameBoard.movePiece(currPlayerSide, srcPosition, destPosition);
			
			// check if move is safe
			if (isCheck(currPlayerSide)) {
				// move is not safe
				notSafe = true;
			}
			
			// restore move
			gameBoard.movePiece(currPlayerSide, destPosition, srcPosition);

		} else { // destination piece holds another piece

			// check if same team
			if (gameBoard.getPiece(destPosition.x, destPosition.y).sameSide(gameBoard.getPiece(srcPosition.x, srcPosition.y))) {
				// same team, cannot make move
				return true;
			}
			
			// clone opponent piece at destination position
			Piece pieceBackup = gameBoard.getPiece(destPosition.x, destPosition.y).clone();
			
			// make move
			gameBoard.movePiece(currPlayerSide, srcPosition, destPosition);

			// check if move is safe
			if (isCheck(currPlayerSide)) {
				// move is not safe
				notSafe = true;
			}
			
			// restore move
			gameBoard.movePiece(currPlayerSide, destPosition, srcPosition);
			
			// add opponent piece that was at destination position
			gameBoard.addPiece(destPosition, pieceBackup);
		}
		
		// move is safe
		return notSafe;
	}
	
	/**
	 * Declares Stalemate if game is not in check and no safe move can be done by the attacked player.
	 * Returns True if Stalemate is declared, False if not
	 */
	@Override
	public boolean isStalemate(PlayerSides.Sides currPlayerSide) {
		
		// check if attacked King cannot move but not threatened
		return !isCheck(currPlayerSide) && cannotMove(currPlayerSide);
	}
	
	/**
	 * Declares Checkmate if game is in Check and no safe move can be done by the attacked player.
	 * Returns True if Checkmate is declared, False if not
	 */
	@Override
	public boolean isCheckmate(PlayerSides.Sides currPlayerSide) {
		
		// check if attacked King is threatened and no move can be done
		return isCheck(currPlayerSide) && cannotMove(currPlayerSide);
	}
}